#~ require "fileutils"
require 'observer'

class Product
  # ToDo: надо завести статический список запущенных билдов,
  #   чтобы вычислять зацикленные депендсы и повторные запуски

  #~ include FileUtils
  include Simplicity # здесь перекрываются многие методы типа 'copy'
  include Observable

  attr_reader :name, :current_history_logs

  Custom = $E[:Cst]
  History = $E[:Hst]

  # тут будем инициализировать переменные для этого билда
  def initialize( product_name )

    # запоминаем глобальный логгер для подсовывания дочерним билдам
    #~ @logger = logger
    add_observer @@logger
    # внутренний логгер, для отслеживания локального процесса
    @int_logger = Simlogger.new
    add_observer @int_logger

    # для начала найдем Blueprint этого продукта
    @blueprint = File.join( $E[:BPs], product_name + '.rb' )
    File.exist?(@blueprint) ? \
      @name = product_name :\
      raise("no blueprint for '#{product_name}'")

    # проинитим версию текущим временем
    @version = [*Time.now.strftime("%Y %m %d %H %M %S").split(' ')]


    @history = Product.history_dir( @name )
      mkpath @history if ! File.exists? @history


    @custom = Product.custom_dir( @name )
    #список файлов, для складывания в хистори
    #каждый элемент - ['файл', 'путь в хистори']
    @result_files = []

    # массив стадий, буду хранить в нем Proc
    @stages = []

    # грузим блюпринт
    # будем Эвалить блюпринт продукта тут
    # в блюпринте должны дефайниться стадии stage('stage name'){|obj| действия }
    File.open( @blueprint ){ |f|
      self.instance_eval f.read
    }

    # теперь надо создать директории для сборки, проверить доступность ресурсов?
    @run = File.join( $E[:Run], @name )
      # создаем @run или чистим его
      if File.exists? @run
        rm_rf Dir.glob( File.join( @run, "*.log" ) )
        rm_rf File.join( @run, 'intermediate' )
        rm_rf Dir.glob( File.join( @run, "bin_*") )
      else
        mkpath @run
      end

  end

  # тут будем строить. возвращает путь хистори/main/
  def build
    cur_dir = Dir.pwd
    Dir.chdir @run

    changed; notify_observers( {:type => :build_start, :content => "building #{@name} in #{@run}"} )
    process

    changed; notify_observers( {:type => :history_start, :content => "#{@history}, #{@version.join('.')}"} )

    main, logs = init_history
    #~ Kernel.system( %Q{move /Y *.log "#{logs}} ) # это надо делать в Simlogger
    # дальше не должно быть записей в лог, иначе будет создаваться лишний лог файл, не попадающий в хистори

    # кладем в хистори то, что накопилось в @result_files
    # fn - имя исходного файла
    # h_path - путь в хистори, куда его складывать (всегда считается каталогом, типа при сорхранении в хистори возможность переименовать файл идеологически не нужна)
    @result_files.each{ |fn, h_path|
      mkpath( win_path( File.join(main, h_path) ) ) #if h_path.match(/[\\\/]$/) # hack
      copy fn, File.join(main, h_path) # , File.basename(fn)
      #~ mkpath( win_path( File.join(main, h_path) ) ) if h_path.match(/[\\\/]$/)
      #~ Kernel.system(
        #~ %Q{copy /Y "#{win_path fn}" "#{win_path( File.join(main, h_path) )}" }
      #~ )
    }

    #puts "returning to #{cur_dir}"
    Dir.chdir cur_dir
    changed; notify_observers( {:type => :build_end, :content => "build of #{@name} complite. dir: #{@current_history}"} )
    @int_logger.savelog( File.join( @current_history_logs, "#{@name}.log" ) )
    @current_history # warning: history have to be initialized while build process!!!
  end

#
# Info Interface
#

  # покажем список доступных продуктов. источник - хистори.
  # ToDo: сообщать, что нет блюпринта.
  def self.list
    return Dir.glob($E[:Hst]+'/*').collect{|n| File.basename n }
  end

  # покажем список версий текущего продукта
  def list
    return Dir.glob(@history+'/*').collect{|n| File.basename n }
  end

#======================= PRIVATE =========================
private

  #~ def [] x
    #~ self.instance_variable_get '@'+x.to_s
  #~ end

  #~ def []= x, y
    #~ self.instance_variable_set '@'+x.to_s, y
  #~ end

  def stage(name = 'unnamed', &block)
    @stages.push( { :name => name, :proc => block } )
  end

  def process
    @stages.each{ |s|
      #~ t = Time.now
      #~ puts "stage: #{s[:name]}"
      changed; notify_observers( {:type => 'stage', :content => s[:name]} )
      s[:proc].call( self )
      #~ puts "stage time: #{Time.now - t}"
    }
  end

#
# History
#

  def init_history
    #raise( "no version defined" ) if ! @version or ! @version.kind_of? Array
    #@version = [*Time.now.strftime("%Y %m %d %H %M %S").split(' ')] if ! @version or ! @version.kind_of? Array
    @current_history = File.join( @history, @name + "_#{@version.join('.')}" )
    @current_history_main = File.join( @current_history, 'main' )
      mkpath @current_history_main
    @current_history_logs = File.join( @current_history, 'logs' )
      mkpath @current_history_logs

    return [@current_history_main, @current_history_logs]
  end

  def historize_file( fn, tdn = '' )
    @result_files.push [ fn, tdn ]
  end

  def historize_dir_content( dn, tdn = '' )
    cd = Dir.pwd
    Dir.chdir dn
    Dir.glob("**/*.*").each{ |f|
      @result_files.push [File.join( dn, f), File.join( tdn, File.dirname(f)+'/' )]
    }
    Dir.chdir cd
  end

#
#  Static methods
#
  def self.init( logger )
    raise "'#{logger}' don't respond to update" if ! logger.respond_to? :update
    raise "'#{logger}' don't respond to log" if ! logger.respond_to? :log
    @@logger = logger
  end

  def self.build( p_name )
    if $modifiers.has_key? p_name

      @@logger.log "modifier for #{p_name} = '#{$modifiers[p_name]}'"

      if $modifiers[p_name] == 'latest'
        return get_latest( p_name )
      elsif $modifiers[p_name] == 'new'
        return Product.new( p_name ).build # same as there is no modifier at all
      elsif dir_name = get( p_name, $modifiers[p_name] )
        return dir_name
      end

    else
      @@logger.log "no modifier for '#{p_name}' defined, just building"
      return Product.new( p_name ).build
    end
  end

  def self.custom_dir( p_name )
    File.join( Product::Custom, p_name )
  end
  def self.history_dir( p_name )
    File.join( Product::History, p_name )
  end

  def self.get_latest( p_name )
    File.join( history_dir(p_name),
      Dir.entries( history_dir(p_name) ).select{|f| f.match(/[^\.]/) }.sort{ |a,b|
        version_to_i(a.split('_').last) <=> version_to_i(b.split('_').last)
      }.last
    )
  end

  def self.get( p_name, version )
    ver_str = ''
    if version.kind_of? String
      ver_str = version
    elsif version.kind_of? Array
      ver_str = version.join(".")
    else
      raise "don't know what to do with #{version.inspect}"
    end
    dir = File.join( history_dir(p_name),  p_name + '_' + ver_str )
    raise("can't find dir for #{p_name}, ver: #{ver_str}") if ! File.directory?( dir )
    return dir
  end

  # computes version numeric weight (for right sorting)
  def self.version_to_i( vstr )
    return 0 if vstr.match(/[^\d\.]/)
    total = 0
    vstr.split('.').reverse.each_with_index{|d,i|
      total += d.to_i * 1000 ** (i+1)
    }
    total
  end
end
